//  @file         main.c ~ main source file for in
//  @author       Original: Stephen Brennan ~ Maintained and customized
//                           my Chris Dorman, 2020+ LGPLv2
//  @desc         CDSH, Chris Dorman's SHell! Simplistic, but functional
//					posix unix shell.

#include "main.h"
#include "parse.h"

/*
  List of builtin commands, followed by their corresponding functions.
*/
char *builtin_str[] = {
    "cd",
    "help",
    "exit"
};

int (*builtin_func[]) (char **) = {
    &cdsh_cd,
    &cdsh_help,
    &cdsh_exit
};

int cdsh_num_builtins() {
    return sizeof(builtin_str) / sizeof(char *);
}

/*
  Builtin function implementations.
*/

/*
   @brief Bultin command: change directory.
   @param args List of args.  args[0] is "cd".  args[1] is the directory.
   @return Always returns 1, to continue executing.
*/
int cdsh_cd(char **args)
{
    if (args[1] == NULL) {
        fprintf(stderr, "cdsh: expected argument to \"cd\"\n");
    } else {
        if (chdir(args[1]) != 0) {
            perror("cdsh");
        }
    }
    
    return 1;
}

/*
   @brief Builtin command: print help.
   @param args List of args.  Not examined.
   @return Always returns 1, to continue executing.
*/
int cdsh_help(char **args)
{
    int i;
    printf("Chris Dorman's SHell\n");
    printf("Type program names and arguments, and hit enter.\n");
    printf("The following are built in:\n");

    for (i = 0; i < cdsh_num_builtins(); i++) {
        printf("=>  %s\n", builtin_str[i]);
    }
 
    printf("Use the man command for information on other programs.\n");
    return 1;
}

/*
   @brief Builtin command: exit.
   @param args List of args.  Not examined.
   @return Always returns 0, to terminate execution.
*/
int cdsh_exit(char **args)
{
    return(0);
}

/*
  @brief Launch a program and wait for it to terminate.
  @param args Null terminated list of arguments (including program).
  @return Always returns 1, to continue execution.
*/
int cdsh_launch(char **args)
{
    pid_t pid;
    int status;

    pid = fork();
    if (pid == 0) {
        // Child process
        if (execvp(args[0], args) == -1) {
            perror("cdsh");
        }
        
        exit(EXIT_FAILURE);
    
    } else if (pid < 0) {
            // Error forking
	        perror("cdsh");
        } else {
            // Parent process
            do {
                waitpid(pid, &status, WUNTRACED);
            } while (!WIFEXITED(status) && !WIFSIGNALED(status));
    }

    return 1;
}

/*
   @brief Execute shell built-in or launch program.
   @param args Null terminated list of arguments.
   @return 1 if the shell should continue running, 0 if it should terminate
*/
int cdsh_execute(char **args)
{
    int i;

    if (args[0] == NULL) {
        // An empty command was entered.
        return 1;
    }

    for (i = 0; i < cdsh_num_builtins(); i++) {
        if (strcmp(args[0], builtin_str[i]) == 0) {
            return (*builtin_func[i])(args);
        }
    }
 
    return cdsh_launch(args);
}

/*
   @brief Read a line of input from stdin.
   @return The line from stdin.
*/
char *cdsh_read_line(void)
{
    #ifdef CDSH_USE_STD_GETLINE
    char *line = NULL;
    ssize_t bufsize = 0; // have getline allocate a buffer for us
    if (getline(&line, &bufsize, stdin) == -1) {
        if (feof(stdin)) {
            exit(EXIT_SUCCESS);  // We recieved an EOF
        } else  {
            perror("cdsh: getline\n");
            exit(EXIT_FAILURE);
        }
    }
    return line;
    #else
    #define CDSH_RL_BUFSIZE 1024
    int bufsize = CDSH_RL_BUFSIZE;
    int position = 0;
    char *buffer = malloc(sizeof(char) * bufsize);
    int c;

    if (!buffer) {
        fprintf(stderr, "cdsh: allocation error\n");
        exit(EXIT_FAILURE);
    }

    while (1) {
        // Read a character
        c = getchar();

        if (c == EOF) {
            exit(EXIT_SUCCESS);
        } else if (c == '\n') {
            buffer[position] = '\0';
            return buffer;
        } else {
            buffer[position] = c;
        }
        
        position++;

        // If we have exceeded the buffer, reallocate.
        if (position >= bufsize) {
            bufsize += CDSH_RL_BUFSIZE;
            buffer = realloc(buffer, bufsize);
            if (!buffer) {
                fprintf(stderr, "cdsh: allocation error\n");
                exit(EXIT_FAILURE);
            }
        }
    }
    #endif
}

#define CDSH_TOK_BUFSIZE 64
#define CDSH_TOK_DELIM " \t\r\n\a"
/*
   @brief Split a line into tokens (very naively).
   @param line The line.
   @return Null-terminated array of tokens.
*/
 
char **cdsh_split_line(char *line)
{
    int bufsize = CDSH_TOK_BUFSIZE, position = 0;
    char **tokens = malloc(bufsize * sizeof(char*));
    char *token, **tokens_backup;

    if (!tokens) {
        fprintf(stderr, "cdsh: allocation error\n");
        exit(EXIT_FAILURE);
    }

    token = strtok(line, CDSH_TOK_DELIM);
    while (token != NULL) {
        tokens[position] = token;
         position++;

        if (position >= bufsize) {
            bufsize += CDSH_TOK_BUFSIZE;
            tokens_backup = tokens;
            tokens = realloc(tokens, bufsize * sizeof(char*));
            if (!tokens) {
		        free(tokens_backup);
                fprintf(stderr, "cdsh: allocation error\n");
                exit(EXIT_FAILURE);
            }
        }

        token = strtok(NULL, CDSH_TOK_DELIM);
    }
    tokens[position] = NULL;
    return tokens;
}

/*
   @brief Loop getting input and executing it.
*/
void cdsh_loop(void)
{
    char *line;
    char **args;
    int status;

    do {
        printf("$ ");
        line = cdsh_read_line();
        args = cdsh_split_line(line);
        status = cdsh_execute(args);

        free(line);
        free(args);
    } while (status);
}

/**
   @brief Main entry point.
   @param argc Argument count.
   @param argv Argument vector.
   @return status code
 */
int main(int argc, char **argv)
{
    // Load config files, if any.
    if (argc == 1) {
		// If cdsh is called by itself, just pull prompt up
		cdsh_loop();
    } else if(argc > 2 || !strcmp(argv[1], "-?") || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        printf("usage: cdsh [file location]\n"
	    "Example: cdsh /path/to/script.sh \n");
	    fflush(stdout);
        cdsh_exit(0); // exit
    } else if (argc == 2 && file_exists(argv[1])==0) {
        parse(argv[1]);
    }

    // Perform any shutdown/cleanup.
    return EXIT_SUCCESS;
}

